#include "application-executor.hpp"

#include <sys/wait.h>
#include <fcntl.h> // O_APPEND, O_WRONLY, etc..
#include <cstring> // strerror, strcmp
#include <errno.h> // errno
#include <iostream>
#include <fstream>
#include <sstream>


int runExecutable(char *args[])
{
    pid_t parent_pid;
    pid_t child_pid;

    child_pid= fork();
    if(child_pid == 0)
    {
        /*
         * Now we are in the child process.
         */

        // Open a file and get a file descriptor to it so I can use dub2 to get stdout and stderr.
        int fd= open("/var/log/application_messages", O_APPEND | O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);

        // Write stdout to /var/log/application_messages
        if (dup2(fd, STDOUT_FILENO) == -1)
        {
            std::string error("dup2(fd, STDOUT_FILENO) ");
            error.append(strerror(errno));

            throw Error(error);
        }

        // Write stderr to /var/log/application_messages
        if (dup2(fd, STDERR_FILENO) == -1)
        {
            std::string error("dup2(fd, STDERR_FILENO) ");
            error.append(strerror(errno));

            throw Error(error);
        }

        // Actually run the app now.
        execvp(args[0], args);

        /* If execvp returns, it must have failed. */

        close(fd);

        std::string error("application-executor can not execute ");
        error.append(*args);

        throw Error(error);
    }
    else
    {
        int exit_code;

        /*
         * Wait for the child to terminate.
         * This is run by the parent.
         */
        if ((parent_pid= wait(&exit_code)) == -1)
        {
            perror("wait()");
        }
        else
        {
            // Check that the child terminated normally.
            if(WIFEXITED(exit_code))
            {
                return WEXITSTATUS(exit_code);
            }

            // Check if the child terminated by a signal.
            else if (WIFSIGNALED(exit_code))
            {
                std::stringstream sstr(std::stringstream::in | std::stringstream::out);

                sstr    << (long)child_pid << "terminated because it didn't catch signal number "
                        << WTERMSIG(exit_code) << std::endl;

                throw Error(sstr.str());
            }
        }
    }
}


int main(int argc, char *argv[])
{
    // The value that the app actually returned.
    int     app_return_value=           -13;

    // The value the app is supposed to return.
    int     app_expected_return_value=  -13;

    std::fstream file;

    try
    {
        file.open("/var/log/status_messages", std::fstream::app | std::fstream::in | std::fstream::out);

        // Check if, either the last argument was not a number or the arguments give were less than three.
        if (argc < 3)
        {
            std::string str("application-executor requires 3 arguments!\n");
            str += "application-executor usage error:\t./application-executor \"command [arguments] ...\" <expected return value>";

            throw Error(str);
        }

        /*
         * The cd command is executed using chdir(), not excecvp. For now,
         * I don't see any reason to use cd.
         */
        if ((strcmp (argv[1], "cd") == 0))
        {
            std::string str("application-executor usage error:\tcd command is not supported.");

            throw Error(str);
        }

        /*
         * Using stringstream instead of atoi to transform the last argument,
         * on the command line. The reason is, atoi returns a value of 0 if
         * the converson was unnsuccessful. However, 0 may be a legitimate
         * application return value.
         */
        std::stringstream sstr(std::stringstream::in | std::stringstream::out);
        sstr << argv[argc - 1] ;

        // Save the last argument. It is the expected return value.
        sstr >> app_expected_return_value;

        // Check if, either the last argument was not a number or the arguments give were less than three.
        if (((sstr.rdstate() & std::stringstream::failbit ) != 0))
        {
            std::string str;

            str= "application-executor usage error:\tWas the last argument a number?\n";
            str += "application-executor usage:\t./application-executor \"command [arguments] ...\" <expected return value>";

            throw Error(str);
        }


        /*
         * Make a new array out of argv by removing first element and
         * making the second one, the first elmement in the new array.
         */
        char* args[argc-1];

        // Reuse my stringstream
        sstr.clear();
        sstr.str("");

        for (int i=0, x=1; i < argc-1; i++, x++)
        {
            // Make a string for reporting.
            sstr << argv[x] << ' ';

            args[i]= argv[x];
        }
        // The last element has to be null for execvp.
        args[argc-2]= '\0';

        // Since the correct agruments were given, announce the application is starting.
        file << "Application: Starting: \"" << sstr.str() << "\"" << std::endl;


        //Finally, run the app.
        app_return_value= runExecutable(args);

        if (app_return_value != app_expected_return_value)
        {
            //return app_return_value; //Don't bother returning the return value. If it's not expected then return 13.

            throw Error(sstr.str() + " did not return the expected value.");
        }
        else
        {
            //file << "application-executor success! " << sstr.str() << " completed successfully" << std::endl;
            file << "Application: Success." << std::endl;

            return app_return_value;
        }
    }
    catch (Error& e)
    {
        file << "Application: error: " << e.what() << std::endl;
        file.close();

        return 13;
    }
}